package quickup;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.SwingUtilities;

public class HttpFileUploader extends Thread {
	
	private File f;
	private HttpDestination dest;
	private JSONObject metadata;
	private ArrayList<ProgressListener> trackers;
	private String field_name;
	private String status = "ready";
	private double progress = 0.0;
	
	public HttpFileUploader(File file, String name, JSONObject json, HttpDestination hd) {
		f = file;
		dest = hd;
		metadata = json;
		field_name = name;
		trackers = new ArrayList<ProgressListener>();
	}
	
	public void addProgressListener(ProgressListener pl) {
		trackers.add(pl);
	}
	
	public void removeProgressListener(ProgressListener pl) {
		trackers.remove(pl);
	}
	
	private void fireStatusChange(String msg) {
		status = msg;
		Iterator<ProgressListener> watchers = trackers.iterator();
		while(watchers.hasNext())
			watchers.next().onStatusChange(this, msg);
	}
	
	private void fireProgressChange(int pos, int ttl) {
		progress = (double)pos / ttl;
		Iterator<ProgressListener> watchers = trackers.iterator();
		while(watchers.hasNext())
			watchers.next().onProgressChange(this, pos, ttl);
	}
	
	private void fireOperationFinished() {
		Iterator<ProgressListener> watchers = trackers.iterator();
		while(watchers.hasNext())
			watchers.next().onOperationFinished(this);
	}
	
	public void run() {
		HttpURLConnection.setFollowRedirects(false);
		HttpURLConnection conn = null;
	
		// Authentication hash
		String authHash = null;
		if (dest.isAuthRequired()) {
			String s = dest.getUsername() + ":" + dest.getPassword();
			authHash = new sun.misc.BASE64Encoder().encode(s.getBytes());
		}

		// filename with prefix
		String fileName = f.getName();

		try {
			// create connection
			conn = (HttpURLConnection) dest.getURL().openConnection();

			// Authorize connection if necessary
			if (dest.isAuthRequired()) {
				conn.setDoInput(true);
				conn.setRequestProperty("Authorization", "Basic " + authHash);
				conn.connect();
				conn.disconnect();
			}

			// Create and setup our HTTP POST connection.
			conn.setRequestMethod("POST");
			// boundary string
			String boundary = "boundary220394209402349823";
			// tail string
			String tail = "\r\n--" + boundary + "--\r\n";
			// Set content type
			conn.setRequestProperty("Content-Type","multipart/form-data; boundary=" + boundary);
			// We intend to send output to the server.
			conn.setDoOutput(true);
			// Create metadata section
			String metadataPart = "";
			if(metadata != null) {
				Iterator json = metadata.keys();
				while(json.hasNext()) {
					String name = json.next().toString();
					metadataPart += "--" + boundary + "\r\n"
					+ "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n"
					+ metadata.get(name) + "\r\n";
				}
			}

			// Create file data header
			String fileHeader1 = "--" + boundary + "\r\n"
					+ "Content-Disposition: form-data; name=\"" + field_name + "\"; filename=\"" + fileName + "\"\r\n"
					+ "Content-Type: application/octet-stream\r\n"
					+ "Content-Transfer-Encoding: binary\r\n";

			long fileLength = f.length() + tail.length();
			String fileHeader2 = "Content-length: " + fileLength + "\r\n";
			String fileHeader = fileHeader1 + fileHeader2 + "\r\n";

			// non-binary part of message
			String stringData = metadataPart + fileHeader;

			// Length of whole request
			long requestLength = stringData.length() + fileLength;
			conn.setRequestProperty("Content-length", "" + requestLength);

			// prevent buffering so that the progress bar actually
			// displays the upload progress
			conn.setFixedLengthStreamingMode((int) requestLength);

			// Connect to the server.
			fireStatusChange("connecting...");
			conn.connect();

			// Initialize output stream
			DataOutputStream out = new DataOutputStream(conn.getOutputStream());

			// write header
			fireStatusChange("uploading...");
			out.writeBytes(stringData);
			out.flush();

			// record length for progress
			final int filesize = (int) f.length();

			int progress = 0;
			int bytesRead = 0;
			byte b[] = new byte[1024];
			BufferedInputStream bufin = new BufferedInputStream(new FileInputStream(f));
			while ((bytesRead = bufin.read(b)) != -1) {
				if (Thread.currentThread().isInterrupted()) {
					// stop....
				}

				out.write(b, 0, bytesRead);
				out.flush();
				progress += bytesRead;

				// update progress bar
				final int p = progress;
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						fireProgressChange(p,filesize);
					}
				});
			}

			// Write closing boundary and close stream
			out.writeBytes(tail);
			out.flush();
			out.close();

			// Get server response
			BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			String line;
			String response = "";
			while ((line = rd.readLine()) != null) {response += line;}
			fireStatusChange(response);
		} catch (Exception e) {
			fireStatusChange(e.getMessage());
		} finally {
			if (conn != null) conn.disconnect();
			fireOperationFinished(); // very important
		}
	}

	public String getFileName() {return f.getAbsolutePath();}
	public String getUploadStatus() {return status;}
	public double getProgress() {return progress;}
	public String toString() {
		return "File Upload: " + f.getName() 
			+ " [" + status + "] (" 
			+ Math.round(progress*100) + "%)";
	}
}
